package com.liuyun.asymmetric.rsa;

import lombok.SneakyThrows;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Security;
import java.security.interfaces.RSAPrivateCrtKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;

/**
 * <p>
 * Copyright: (C), 2021/1/24 16:26
 * <p>
 * <p>
 * Company Information & Data Security Solutions Co., Ltd.
 *
 * @author LiuYun liujw@idss-cn.com
 * @version 1.0
 */
public class RsaUtil {
    static {
//        Security.addProvider(new BouncyCastleProvider());
    }

    public static final String KEY_ALGORITHM = "RSA";
    // jdk
//        public static final String RSA_ALGORITHM = "RSA/ECB/NoPadding";
        public static final String RSA_ALGORITHM = "RSA/ECB/PKCS1Padding";

//        public static final String RSA_ALGORITHM = "RSA/ECB/OAEPWITHSHA1AndMGF1Padding";
//        public static final String RSA_ALGORITHM = "RSA/ECB/OAEPWITHMD5AndMGF1Padding";
//        public static final String RSA_ALGORITHM = "RSA";
//        public static final String RSA_ALGORITHM = "RSA/ECB/PKCS1Padding";
//        public static final String RSA_ALGORITHM = "RSA/ECB/OAEPPadding";
//    public static final String RSA_ALGORITHM = "RSA/NONE/PKCS1Padding";
    //    public static final int KEY_SIZE = 512;
//    public static final int KEY_SIZE = 768;
    public static final int KEY_SIZE = 1024;
    public static final String PUBLIC_KEY = "publicKey";
    public static final String PRIVATE_KEY = "privateKey";

    /**
     * 产生密钥对
     *
     * @return
     * @throws NoSuchAlgorithmException
     */
    public static Map<String, Key> initKey() throws NoSuchAlgorithmException {
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KEY_ALGORITHM);
        keyPairGenerator.initialize(KEY_SIZE);
        KeyPair keyPair = keyPairGenerator.generateKeyPair();
        RSAPrivateCrtKey privateKey = (RSAPrivateCrtKey) keyPair.getPrivate();
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        Map<String, Key> keyMap = new HashMap<>(2);
        keyMap.put(PUBLIC_KEY, publicKey);
        keyMap.put(PRIVATE_KEY, privateKey);
        return keyMap;
    }

    public static byte[] getPublicKey(Map<String, Key> keyMap) {
        return keyMap.get(PUBLIC_KEY).getEncoded();
    }

    public static byte[] getPrivateKey(Map<String, Key> keyMap) {
        return keyMap.get(PRIVATE_KEY).getEncoded();
    }

    /**
     * 私钥加密
     *
     * @param data
     * @param key
     * @return
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeySpecException
     * @throws NoSuchPaddingException
     * @throws InvalidKeyException
     * @throws BadPaddingException
     * @throws IllegalBlockSizeException
     */
    @SneakyThrows
    public static byte[] encryptByPrivateKey(byte[] data, byte[] key) {
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(key);
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
//        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); // 默认RSA算法
//        Cipher cipher = Cipher.getInstance(RSA_ALGORITHM,"BC");
        Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
        cipher.init(Cipher.ENCRYPT_MODE, privateKey);
        return cipher.doFinal(data);
    }

    /**
     * 公钥解密
     *
     * @param data
     * @param key
     * @return
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeySpecException
     * @throws NoSuchPaddingException
     * @throws InvalidKeyException
     * @throws BadPaddingException
     * @throws IllegalBlockSizeException
     */
    @SneakyThrows
    public static byte[] decryptByPublicKey(byte[] data, byte[] key) {
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(key);
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        PublicKey publicKey = keyFactory.generatePublic(keySpec);
//        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
//        Cipher cipher = Cipher.getInstance(RSA_ALGORITHM,"BC");
        Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, publicKey);
        return cipher.doFinal(data);
    }


    /**
     * 公钥加密
     *
     * @param data
     * @param key
     * @return
     */
    @SneakyThrows
    public static byte[] encryptByPublicKey(byte[] data, byte[] key) {
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(key);
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        PublicKey publicKey = keyFactory.generatePublic(keySpec);

//        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
//        Cipher cipher = Cipher.getInstance(RSA_ALGORITHM,"BC");
        Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        return cipher.doFinal(data);
    }

    /**
     * 密钥解密
     *
     * @param data
     * @param key
     * @return
     */
    @SneakyThrows
    public static byte[] decryptByPrivateKey(byte[] data, byte[] key) {
        PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(key);
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);

//        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
//        Cipher cipher = Cipher.getInstance(RSA_ALGORITHM,"BC");
        Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        return cipher.doFinal(data);
    }

    @SneakyThrows
    public static void main(String[] args) {
        Map<String, Key> keyMap = initKey();
        byte[] plaintextBytes = "12345".getBytes();
        byte[] encrypt = encryptByPrivateKey(plaintextBytes, getPrivateKey(keyMap));
        byte[] decrypt = decryptByPublicKey(encrypt, getPublicKey(keyMap));
        System.out.println(new String(decrypt));

    }
}
